#include <Channel.h>
#include <ChannelLog.h>
#include <Scheduler.h>
#include <csp_debug.h>
#include <csp_macros.h>
#include <csp_protocol_msg.h>
#include <csp_shared_types.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <libspe2.h>
#include <pthread.h>
#include <malloc_align.h>
#include <string>
#include <Alting.h>


using namespace std;

Channel::Channel(int type_, long id_)
{
	type = type_;
	id = id_;
	r_pid = 0, w_pid = 0;
	msg_size_in_channel = 0;
	poison = false;
	altHook = NULL;
	altIdx = -1;
	pthread_mutex_init(&chan_lock, NULL);
	pthread_mutex_init(&alt_lock, NULL);

	char *c_type;
	switch(type) {
		case ONE2ONE_CHANNEL:
			c_type = "ONE2ONE_CHANNEL";
			break;
		case ANY2ONE_CHANNEL:
			c_type = "ANY2ONE_CHANNEL";
			break;
		case ONE2ANY_CHANNEL:
			c_type = "ONE2ANY_CHANNEL";
			break;
		case ANY2ANY_CHANNEL:
			c_type = "ANY2ANY_CHANNEL";
			break; /* No checks. */
		default:
			_PRINT_EXIT("Unknown channeltype (%X) with id=%lX.\n", type, id);
			exit(-1);
			break;
	}
	//printf("%s created with id=%lX.\n",c_type, id);

}

Channel::~Channel()
{
}


bool Channel::IsPoisoned()
{
	return poison;
}


void
Channel::CheckChannelOperation(bool read, ProcessComposition* process, ls_size_t _msg_size)
{
	int32_t *pid;

	if(read){
		pid = &r_pid;
	}else{ /* write */
		pid = &w_pid;
	}

	if( !(*pid) ){
		*pid = process->GetPID();	
	}

	if(0 == msg_size_in_channel){
		msg_size_in_channel = _msg_size;
	}else if( msg_size_in_channel != _msg_size ){
		_PRINT_EXIT("Message size (%i) does not equal message size in channel (%i).\n", _msg_size, msg_size_in_channel);	
	}

	switch(type) {
		case ONE2ONE_CHANNEL:
			if( *pid != process->GetPID() ){
				_PRINT_EXIT("More than two processes tried to communicate on a 0ne2One-channel with id=%lX (r_pid=%i, w_pid=%i).\n", id, r_pid, w_pid);}
			break;
		case ANY2ONE_CHANNEL:
			if( read && *pid != process->GetPID()){
				_PRINT_EXIT("More than one process tried to read from a Any2One-channel with id=%lX.\n", id);}
			break;
		case ONE2ANY_CHANNEL:
			if( !read && *pid != process->GetPID()){
				_PRINT_EXIT("More than one process tried to write to a 0ne2Any-channel with id=%lX.\n", id);}
			break;
		case ANY2ANY_CHANNEL:
			break; /* No checks. */
		default:
			_PRINT_EXIT("Unknown channeltype (%X) with id=%lX.\n", type, id);
			exit(-1);
			break;
	}

}


void Channel::Release(ProcessQueue &links, int op_ret_val)
{

	ProcessComposition *process;
	Scheduler *scheduler = Scheduler::Instance();

	while( links.size() ){
		process = &links.pop();

		/* Make sure process is in EA. */
		process->WaitForDMACompletion(11, -1);

		/* Set return value of process' channel op. */
		process->GetPCB()->op_ret_val = op_ret_val;

		/* Resume process. */
		process->GetPCB()->state = STATE_YIELD;

		//printf("Poisoned channel: released process w. pid=%i.\n", cl.process->GetPID());

		/* Schedule process. */
		scheduler->put( *(process), STATE_YIELD );
	}
}


void Channel::Poison()
{
	poison = true;

	/* If a process becomes poisoned all processes waiting 
	 * on the channel has to be released and informed about
	 * the posioning. */
	Release(w_procs, CHANNEL_POISON);
	Release(r_procs, CHANNEL_POISON);
}


ProcessComposition*
Channel::DoChannelOperation(bool read, ProcessComposition *process, ls_addr_t ls_addr, ls_size_t size)
{
	//channel_link_t* ret = NULL;
	//Queue <channel_link_t> *opposite_end, *my_end;
	ProcessQueue *opposite_end, *my_end;
	ProcessComposition *ret = NULL;

	Alting *alting; int idx;

	CheckChannelOperation(read, process, size);

	if(read){
		opposite_end = &w_procs; my_end = &r_procs;
	}else{ /* write */
		opposite_end = &r_procs; my_end = &w_procs;
	}

	

	/* */
	pthread_mutex_lock (&alt_lock);
	alting = altHook;
	idx    = altIdx;
	pthread_mutex_unlock (&alt_lock);

	/* Special case: reading end is alting. */
	if( !read && alting ){

		printf("channel: alting channel!\n");

		/* This is safe, even though we're disabled. */
		ret = alting->AquireWrite(idx);

		if( ret ){ /* reading process is alting. */
			
			printf("channel: Got link!\n");
			
			ASSERT( !opposite_end->size() );

			return ret;	
		}
		printf("channel: did not get link\n");
	}

	//char op = read ? 'r' : 'w';
	//ChannelLog *channelLog = ChannelLog::Instance();	

	/* Process must wait for corresponding operation */
	if( !opposite_end->size() ){ 
/*
		ret	= (channel_link_t*) malloc(sizeof(channel_link_t));
		ret->process = process;
		ret->ls_addr = ls_addr;
		ret->return_value = CHANNEL_SUCCESS;
*/
		//channelLog->Increment(process, id, op);

		process->channel_link.ls_addr = ls_addr;
		process->channel_link.return_value = CHANNEL_SUCCESS;
		my_end->push(*process);
	}else{ /* Corresponding operation has happened. */

		//channelLog->Decrement(process, id, op);

		return &opposite_end->pop();
	}
	return NULL;
}



ProcessComposition*
Channel::Write(ProcessComposition *process, ls_addr_t w_addr, ls_size_t size)
{
	return DoChannelOperation(false, process, w_addr, size);
}


ProcessComposition*
Channel::Read(ProcessComposition *process, ls_addr_t w_addr, ls_size_t size)
{
	return DoChannelOperation(true, process, w_addr, size);
}


void Channel::Lock()
{
	pthread_mutex_lock (&chan_lock);
}

void Channel::Unlock()
{
	pthread_mutex_unlock (&chan_lock);
}

long
Channel::GetID()
{
	return id;
}

bool Channel::AltingEnable(Alting *alt, int idx)
{
	printf("Channel enabled size=%i.\n", w_procs.size());
	pthread_mutex_lock (&alt_lock);
	altHook = alt;
	altIdx = idx;
	pthread_mutex_unlock (&alt_lock);

	return w_procs.size() > 0;
}


bool Channel::AltingDisable()
{
	printf("Channel disabled, size=%i, pri=%i\n", 
		w_procs.size(), altIdx);

	pthread_mutex_lock (&alt_lock);
	altHook = NULL;
	altIdx = -1;
	pthread_mutex_unlock (&alt_lock);

	return w_procs.size() > 0;
}


ProcessComposition* Channel::Accept()
{
	ASSERT( w_procs.size() );

	return &w_procs.pop();
}


